package fr.umlv.ig.model;

import java.awt.Point;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * Represents Station on the metro. 
 * Itinerary object is final with a public visibility. You can 
 * modified the object just if you are in his package.
 * @author Jenseric Catbgan
 * @author William Choy
 *
 */
public class Station implements Comparable<Station>{
	/**Name of the station.*/
	private final String name;

	/**Line of the station.*/
	private final Line line;
	
	/**Boolean to say if the line is selected or not.*/
	private boolean isSelected;
	
	private final Map<Station, Boolean> mapBusy= new HashMap<Station, Boolean>();
	
	/**Coordinate of the station.*/
	private final Point coordinate;
	
	/**Boolean to know if the line is opened or not.*/
	private boolean opened;
	
	/**waiting time in the station*/
	private int waitingTime; 
	
	/**Map of neighbor stations*/
	private final Map<Station, Station> nextStations = new HashMap<Station, Station>();
	
	/**Map of waiting time of each neighbor stations*/
	private final Map<Station, Integer> nextWaiting = new HashMap<Station, Integer>();
	
	/**
	 * Initialize a Station
	 * @param name the name of the station.
	 * @param x the x coordinate of the station.
	 * @param y the y coordinate of the station.
	 * @param line the line of the station.
	 * @param waitingTime the waitingTime of the station.
	 * 
	 * @throws NullPointerException 
	 *         the line is null.
	 * @throws IllegalArgumentException
	 *         the waitingTime is negative.
	 */
	public Station(String name, int x, int y, Line line, int waitingTime) {
		if(name == null) {
			throw new NullPointerException("The mame is null");
		}
		if(waitingTime < 0) {
			throw new IllegalArgumentException("The time is negative");
		}
		this.name =name;
		this.waitingTime = waitingTime;
		coordinate = new Point(x, y);
		opened = true;
		this.line= line;
	}
	
	
	/**
	 * Returns the name of the Station.
	 * @return the name of the Station.
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Returns the coordinate of the Station.
	 * @return the coordinate of the Station.
	 */
	Point getCoordinate() {
		return coordinate;
	}
	
	/**
	 * Returns the coordinate X of the Station.
	 * @return the coordinate X of the Station.
	 */
	int getX() {
		return coordinate.x;
	}

	/**
	 * Returns the coordinate Y of the Station.
	 * @return the coordinate Y of the Station.
	 */
	int getY() {
		return coordinate.y;
	}
	
	/**
	 * Sets the boolean which indicate if the station is opened or not.
	 * @param opened the new boolean which indicate if the station is opened or 
	 *        not.
	 */
	void setOpened(boolean opened) {
		this.opened = opened;
	}
	
	/**
	 * Returns true if the station is opened else false.
	 * @return true if the station is opened else false.
	 */
	boolean isOpened() {
		return opened;
	}
	
	/**
	 * Sets the waitingTime of the station.
	 * @param waitingTime the new waitingTIme of the station.
	 */
	void setWaitingTime(int waitingTime) {
		if(waitingTime < 0) {
			throw new IllegalArgumentException("the time is negative");
		}
		this.waitingTime = waitingTime;
	}

	/**
	 * Returns the waitingTime of the model.
	 * @return the waitingTime of the model.
	 */
	int getWaitingTime() {
		return waitingTime;
	}

	/**
	 * Returns the neighbor station by a direction.
	 * @param direction the direction of the next station.
	 * @return the neighbor station by a direction.
	 */
	Station getNextStation(Station direction) {
		return nextStations.get(direction);
	}
	
	/**
	 * Returns the map of neighbor stations of the station.
	 * @return
	 */
	Map<Station,Station> getMapNextStations() {
		return Collections.unmodifiableMap(nextStations);
	}
	
	/**
	 * Adds a neighbor station by a direction.
	 * @param direction the direction of the next station to add.
	 * @param nextStation the next station to add.
	 * @throws NullPointerException
	 *         if the direction or the nextStation are null
	 */
	void addNextStation(Station direction, Station nextStation) {
		if(direction==null || nextStation ==null) {
			throw new NullPointerException("One or most stations are null");
		}
		mapBusy.put(direction, false);
		nextWaiting.put(nextStation, 100);
		nextStations.put(direction, nextStation);
	}
	
	
	/**
	 * Sets the waiting time between the station and the neighbor station in 
	 * parameter.
	 * @param station the neighbor station in parameter.
	 * @param time the waiting time between the current station and the neighbor.
	 * @throws IllegalArgumentException
	 *         if the station is not a neighbor of the current Station or if 
	 *         the time is negative.
	 */
	void setWaitingTime(Station station,int time) {
		if(!nextWaiting.containsKey(station)) {
			throw new IllegalArgumentException("The station is not a next");
		}
		if(time < 0) {
			throw new IllegalArgumentException("The time is negative");
		}
		nextWaiting.remove(station);
		nextWaiting.put(station,time);
	}
	
	
	/**
	 * Returns the waiting time between the current station and a neighbor station.
	 * @param nextStation the neighbor station.
	 * @return the waiting time between the current station and the neighbor 
	 *         station in parameter.
	 * @throws IllegalArgumentException
	 *         The station in parameter is not in the line
	 */
	int getWaitingTime(Station nextStation) {
		if(!nextWaiting.containsKey(nextStation)) {
			throw new IllegalArgumentException("The station is not in the Line");
		}
		return nextWaiting.get(nextStation);
	}

	
	@Override
	public boolean equals(Object obj) {
		if(obj == this) {
			return true;
		}
		if(!(obj instanceof Station)) {
			return false;
		}
		Station s = (Station) obj;
		return name.equals(s.name) && coordinate.equals(s.coordinate); 
	}
	
	
	Line getLine() {
		return line;
	}
	
	
	@Override
	public int hashCode() {
		return name.hashCode();
	}
	
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("[name : ").append(name).append("\n");
		builder.append("coordinate : (").append(coordinate.x).append(",").
			append(coordinate.y).append(")\n");
		builder.append("open : ").append(opened).append("\n");
		builder.append("waitingTime : ").append(waitingTime).append("\n");
		int i=1;
		for(Station next : nextStations.values()) {
			builder.append("neighbor "+i+" : "+next.name ).append("\n");
			i++;
		}
		builder.append("]");
		return builder.toString();
			   
	}

	@Override
	public int compareTo(Station o) {
		if(o == null) {
			return -1;
		}
		return name.compareTo(o.name);
	}


	/**
	 * Returns true if the station is selected, else false.
	 * @return true if the station is selected, else false.
	 */
	boolean isSelected() {
		return isSelected;
	}

	/**
	 * Sets the boolean which indicate if the station is selected or not.
	 * @param isSelected the boolean to indicate if the station is selected or not.
	 */
	void setSelected(boolean isSelected) {
		this.isSelected = isSelected;
	}

	void changedState(Station direction) {
		boolean bool = mapBusy.get(direction);
		mapBusy.put(direction, !bool);
	}
	
	boolean getState(Station direction) {
		return mapBusy.get(direction);
	}
	
}
